02. Tools 与 Skills 系统设计
本章高频面试题
- 什么是 Tool?什么是 Skill?两者和 Sub-agent 的区别是什么?
- Tool 的粒度应该怎么设计?一个大工具还是多个小工具?
- 工具很多时,如何设计工具选择系统?Progressive disclosure 和分层路由各解决什么问题?
- 如何优化 Tool Schema 和 description,提高 Tool Calling 的准确率?
- 什么是负向约束?description 里的负向约束真的能被模型遵守吗?
- Tool 的错误返回应该怎么设计,才能让 Agent 自修复而不是陷入死循环?
- Tool 输出太大会怎样?怎么做 context budget 治理?
- 异步和长时任务应该怎么暴露给模型?
- Skills 和 Prompt 有什么区别?为什么不能把所有能力都写成长 Prompt?
- Skills 多的时候怎么做路由、检索、版本治理和淘汰?
- Tool schema 频繁变更会怎样影响 prompt caching?怎么设计稳定的 schema?
- Tool 和 Skill 的评估指标分别是什么?评估数据集怎么构造?
- 如何设计安全可控的工具平台?
1. 什么是 Tool
Tool 可以理解成 Agent 可以调用的一个外部能力单元。
它通常具备下面这些特点:
- 输入明确
- 输出明确
- 职责相对单一
- 有边界
- 有副作用时可控
常见的工具包括:
- 搜索网页
- 查询数据库
- 调内部业务 API
- 读取文件
- 发邮件
- 创建工单
- 执行代码
所以 Tool 的本质很像:
给模型暴露出来的一组可控函数或能力接口。
值得提一句的是,2024 年底以来 MCP (Model Context Protocol) 已经成为事实上的跨客户端工具协议。如果你做的是平台级系统,自研 tool registry 和接入 MCP server 通常不是二选一,而是”内部工具走自研 registry,外部/第三方能力走 MCP”。不要低估”能被别人的 Agent 直接接入”这件事带来的生态价值。
2. 什么是 Skill
Skill 在工业界没有 Tool 那么统一的标准定义。
它通常指的是一类”可复用的工作方法模块”。
你可以把 Skill 理解成:
- 一套指令
- 一组执行步骤
- 若干工具的组合使用方式
- 一些资源文件和模板
- 某个领域里被验证过的套路
举例:
- “代码审查 skill”
- “竞品调研 skill”
- “论文阅读总结 skill”
- “安全排查 skill”
Skill 通常不是一个原子动作,而是一种”做事方法”。
在实现上,现代 Skill 越来越趋近于”一个带元数据的目录”:SKILL.md 描述触发条件和执行方法,旁边放引用文档、模板、评估样本。它不是一段注入到系统提示里的长文本,而是一个按需加载的资源包。这一点在 §5 的 progressive disclosure 里会展开。
3. Tool、Skill、Sub-agent 的区别
一个很好记的区分方式是:
- Tool 是”动作”
- Skill 是”方法”
- Sub-agent 是”带独立上下文的专业工种”
再说得更工程一点:
| 层 | 类比 | 独立上下文 | 典型产物 |
|---|---|---|---|
| Tool | 函数/API | 否 | 结构化 JSON |
| Skill | 流程模板/能力包 | 否(共享主 Agent 上下文) | 过程 + 最终输出 |
| Sub-agent | 专职同事 | 是 | 汇报式结果 |
什么时候该拆成 Sub-agent 而不是 Skill?一个简单判据:
- 如果执行过程会产生大量”过程性噪音”(搜索结果、中间草稿、失败重试日志),污染主对话上下文 → Sub-agent
- 如果只是复用某个固定的方法论和资源,过程本身就是主 Agent 该看到的 → Skill
- 如果只是一次原子操作 → Tool
例如”调研三篇 RAG 论文并输出中文总结”这个任务:
Tool 层面:
search_papersget_paper_by_idfetch_pdfextract_sectionswrite_markdown
Skill 层面:
- “论文调研总结 skill”——规定先搜、再筛、再提取方法/实验/结论、最后按固定结构输出比较
Sub-agent 层面(可选):
- 如果每篇论文都要读几十页 PDF、做大量中间推理,就把”单篇论文深读”封装成一个 research sub-agent,主 Agent 只看三份结构化汇报
不要把所有”多步骤”都塞进 Skill。Skill 的过程是主 Agent 要感知的;Sub-agent 的过程是主 Agent 不需要感知的。
4. Tool 系统的方法论
4.1 Tool 设计的第一原则:单一职责
在工程里,Tool 最好遵循单一职责。 也就是说,一个 Tool 最好只做一件事,或者做一类高度相关的小事。
为什么?
因为工具一旦设计得太大、太杂,会带来这些问题:
- 参数很多
- 描述很难写清楚
- 模型容易误调用
- 权限边界不清晰
- 错误恢复困难
一个不太推荐的例子:
customer_ops(action, userId, orderId, refundReason, emailContent, ...)
这种”大一统工具”在 Demo 里很省事,但在生产里通常很难维护。
更推荐的方式:
get_customer_profilelist_customer_ordersget_order_detailcreate_refund_requestsend_customer_email
4.2 Tool 设计的第二原则:输入输出必须结构化,且受预算约束
任何给模型调用的 Tool,输入和输出都应该尽量结构化。
原因很简单:
- 方便校验
- 方便重试
- 方便监控
- 方便把结果接入下游系统
尽量避免让工具返回一大段难以解析的自由文本。 如果确实需要自由文本,也建议外层再包一个结构。
例如:
type SearchPapersResult = {
items: Array<{
id: string;
title: string;
abstract?: string;
year?: number;
url?: string;
}>;
total: number;
source: "arxiv" | "semantic_scholar";
};输出大小必须有预算约束。 结构化不等于可以无限返回——任何 tool 返回值都在和主 Agent 的上下文预算抢位置。实践上有几条硬规矩:
- 单次 tool 返回如果预期会超过约 5k tokens,必须分页或返回 handle。
- 列表查询一律带
limit和next_cursor,不要一次性返回全量。 - 大 payload(PDF、截图、长 JSON、向量搜索全文)采用两段式:“第一步返回
resource_uri和摘要,第二步按需read_resource”。 - 字符串字段尽量给出
truncated: true标记,而不是偷偷截断。
这件事在 Agent 跑长时间 loop 时尤其致命:一个返回 20k tokens 的 search 工具会在 10 步之内把上下文窗口吃满。
4.3 Tool 设计的第三原则:错误返回要”可操作”
大多数文档只讲”怎么返回成功结果”,但生产系统里决定 Agent 能否自修复的是错误消息的质量。
反例:
{ "error": "ValidationError: field 'x' is required" }这种错误会让模型猜:是不是参数名打错了?是不是该改用别的工具?是不是终止?
推荐的结构:
type ToolError = {
error: string; // 机读错误码,如 "missing_field" / "not_found" / "rate_limited"
message: string; // 给模型看的人话说明
hint?: string; // 下一步建议:"call get_customer_profile first to obtain customerId"
retryable: boolean; // 是否值得原样重试
retry_after_ms?: number; // 限流场景下告诉模型等多久
fields?: Record<string, string>; // 哪些字段有问题
};几条经验:
- 错误消息要告诉模型下一步做什么,而不是描述失败细节
retryable必须显式,不要让模型猜- 业务错误(参数缺失、权限不足、资源不存在)比网络错误更常见,单独设计
- 对”同样错误已经出现第 N 次”的情况,内核应该主动打断循环,而不是依赖模型察觉
4.4 Tool 设计的第四原则:把副作用显式分层,并考虑幂等性
工具可以大致分成三类:
- 只读工具
- 低风险写工具
- 高风险写工具
例如:
- 搜索文档:只读
- 保存草稿:低风险写
- 发邮件、退款、删数据:高风险写
这个分层决定了:
- 是否允许自动执行
- 是否需要人工确认
- 是否需要审批流
- 是否需要更强的日志审计
所有写工具还必须考虑幂等性。Agent 的 retry 逻辑、模型的重复调用、上游超时补发,都可能让同一个写操作被触发多次。实践上:
- 所有写工具暴露
idempotency_key参数(通常是 Agent 生成的 UUID) - 服务端基于
idempotency_key做去重,重复调用返回首次结果而不是再执行 - 高风险不可逆动作(打款、删数据)额外引入”软删除 + 补偿动作”机制,保留回滚余地
一个简单判据:如果你这个写工具没有 idempotency_key,那它大概率在生产里会出事。
4.5 Tool 设计的第五原则:写清楚”何时调用”和”何时不要调用”
很多团队会花很多力气写 schema,但对 description 写得很随便。 实际上,在 Tool Calling 场景里,description 往往非常重要。
一个好的 description 至少要包含:
- 这个工具是干什么的
- 什么时候该调用
- 什么时候不该调用
- 参数应该怎么填
- 如果有更合适的工具,应该改用哪个
例如:
import { z } from "zod";
export const searchPapersSchema = z.object({
query: z
.string()
.describe(
"论文检索关键词。优先使用英文技术术语,如 'retrieval augmented generation reinforcement learning'。不要传整句自然语言任务描述;如果用户已经提供 DOI、arXiv ID 或精确标题,不要调用本工具,直接用 get_paper_by_id。"
),
maxResults: z
.number()
.int()
.min(1)
.max(10)
.describe("返回结果上限。默认 5,除非用户明确要求更多。"),
});这里最重要的两个点:
- 正向约束:优先用英文技术术语
- 负向约束:已有 DOI 或精确标题时不要调用本工具,改用
get_paper_by_id
但必须认清负向约束的局限。 description 里的”不要”只是软提示,低能力模型、长上下文、多候选工具同时存在时经常被忽略。真正的硬约束要靠这几层:
- Tool filtering:调用前根据上下文/权限/状态动态过滤工具列表,不该暴露的就不暴露
- Schema 级约束:能用 enum 就别用 free-form string,能用正则就别用 description
- Server 侧校验:服务端拒绝不合法调用,不依赖模型自觉
- Constrained decoding:在关键参数上用 grammar-constrained decoding(如果推理栈支持)
description 里的负向约束只降低误调用率,不要把安全性押在上面。
5. 工具很多时,如何设计 Tool Selection
当系统里只有 5 个工具时,直接全部暴露给模型通常问题不大。 但当工具变成 30 个、50 个、100 个时,问题会明显增加:
- 上下文太长
- 工具说明互相干扰
- 模型选错工具
- 参数填充质量下降
- Prompt cache 命中率下降(见 §7)
5.1 首选方案:Progressive Disclosure(按需加载)
2023 年的主流思路是”用 embedding 检索 top-k 工具塞给模型”。2025 年以来更成熟的做法是 progressive disclosure:
- 系统提示里只挂 catalog:每个工具一行——名字 + 一句话描述,没有完整 schema
- 模型需要时主动调
load_tool/ToolSearch按名字或关键词拉完整 schema - 拉出来的 schema 才参与后续的 tool call
这比”检索召回 top-k”更稳的原因:
- 召回漏了就永远看不见;catalog 保证模型任何时候都知道这个能力存在
- Catalog 很短、很稳定,prompt cache 命中率高
- Schema 变更不污染系统提示前缀,只影响当次按需加载
Anthropic 的 Skills 系统、Claude Code 的 ToolSearch、以及 MCP 的 resource/tool listing 都是这个模式。如果你在自研 Agent 平台,这应该是 tool 超过 20 个之后的默认架构。
5.2 兜底方案:分层路由(当 progressive disclosure 不适用时)
有些场景不适合 progressive disclosure——模型能力弱、tool call 延迟敏感、工具数量极大(上千)。这时分层路由仍然有效:
第一层:任务路由。先判断当前任务大致属于哪一类,例如检索类、CRM 类、分析类、办公自动化类、开发类。这一步可以用规则或轻量分类模型。
第二层:候选工具召回。只从对应工具簇里召回 top-k 候选。召回可以用规则、embedding、BM25、混合检索 + rerank。
第三层:最终决策。把少量候选工具和当前状态给模型做最终选择。
第四层:调用校验。高风险场景加一层校验器,检查工具是否适合当前目标、必填参数是否齐全、是否违反负向约束、是否触发人工审批。
实际工程里这两种方案经常混用:progressive disclosure 做默认,敏感/高成本分支用分层路由做硬兜底。
5.3 工具列表应该是动态的
不管用哪种方案,暴露给模型的工具列表都不应该是静态的。至少要按以下维度动态裁剪:
- 用户权限:当前用户没有绑定 X 账号,就不暴露
x_publish - 工作区状态:onboarding 还没完成,就不暴露
publish_content - 对话阶段:intake 阶段只暴露
ask_user类工具,execution 阶段才开放写工具 - 成本预算:接近 token/延迟预算时主动裁剪昂贵工具
静态 tools.json 是 Demo 方案;生产 Agent 的 tool list 应该由 ToolAssembler 在每次对话 turn 动态组装。
6. 异步与长时工具
文档里绝大多数 tool 示例是同步函数(毫秒到秒级返回)。实际生产里至少有三种形态,设计模式不同:
6.1 同步 tool(< 30s)
直接返回结果。约束:
- 服务端必须有超时(通常 10–30s)
- 超时要返回可操作的错误(
retryable: true)
6.2 异步 tool(分钟到小时级)
典型场景:爬取并分析一个网站、训练小模型、批量生成内容、长任务发布流水线。
不要让 Agent 阻塞等待。两段式设计:
// 第一次调用:提交任务
submit_site_crawl(url) → { task_id: "t_123", status: "running", poll_after_ms: 5000 }
// 后续调用:查询状态
get_task_status(task_id) → { status: "running" | "succeeded" | "failed", result?, error? }更优雅的做法是用 durable execution(Temporal、Inngest、自研)承载长任务,tool 只是把 task_id 返还给 Agent,由运行时负责完成/重试/补偿。主 Agent 可以去做别的事情,结果出来再通过事件驱动回到对话。
6.3 流式 tool(生成类)
典型场景:生成长文、代码执行的逐行输出、搜索的逐条命中。
接口上通常用 SSE 或 chunked response,但给模型看到的仍然是结构化结果——流式是传输层的事,不应该让模型关心。
6.4 Webhook / 事件驱动回调
异步任务完成时,通过事件唤醒 Agent 继续推进主对话,而不是让 Agent 轮询。这需要平台支持”interrupt/resume”——Agent 的状态能被挂起并在事件到达时恢复。
7. Prompt Caching 友好的 Schema 稳定性
Tool schema 通常在系统提示的最前面,它稳定与否直接决定 prompt cache 命中率,而 cache 命中率直接影响成本和延迟(Anthropic 的 cache hit 能把 cached 部分的成本降到 1/10)。
几条工程规矩:
- Schema 变更分版本:新增字段一律 optional,放在末尾,不要中插或重排
- 高频迭代的 description 和稳定的 schema 分离:description 放在可调文案层,schema 放在稳定定义层
- Tool list 排序稳定:即使工具集合不变,顺序变了也会让前缀缓存失效
- 动态裁剪放在 schema 之后:用户相关的动态部分(当前 workspace、绑定状态)放在系统提示的后段,避免污染前缀
- CI 加 schema diff 检测:任何破坏性变更(字段删除、类型变更、重命名)必须显式升级版本并更新回归评估
Prompt cache 不是”做了就有”,而是”架构里刻意维护的资产”。一个团队如果 tool schema 一周改五次,cache 命中率永远上不去。
8. Skill 系统的方法论
8.1 为什么不能把所有能力都写成 Prompt
Prompt 可以描述任务,但 Skill 更适合承载可复用的方法论。
如果你每次都用 Prompt 临时告诉模型”怎么做代码审查”,会遇到这些问题:
- 结果不稳定
- 容易漏步骤
- 很难复用
- 很难版本管理
而把它沉淀成 Skill,就可以明确:
- 触发条件
- 执行步骤
- 必需工具
- 输出模板
- 失败处理方式
8.2 什么样的经验值得升级为 Skill
一个工作方法适合沉淀成 Skill,通常满足三个条件:
- 高复用
- 相对稳定
- 封装后能明显减少重复推理
例如:
- “论文调研总结”
- “安全扫描与漏洞分类”
- “代码 PR 审查”
- “客户邮件撰写”
8.3 Skills 多的时候怎么处理
当 Skills 很少时,你可以直接把它们写在系统提示里。 但当 Skills 很多时,就必须像管理工具一样去管理它们——而且要比工具更激进地用 progressive disclosure,因为单个 skill 的 body 通常比单个 tool schema 大一个数量级。
Skill 至少应该包含这些元数据:
type SkillMetadata = {
name: string;
description: string;
domain: string;
triggerConditions: string[];
antiPatterns: string[];
requiredTools: string[];
expectedOutput: string;
riskLevel: "low" | "medium" | "high";
version: string;
owner: string; // 谁维护——涉及下线决策
lastEvaluatedAt: string; // 最近一次评估通过的时间
};为什么要有 antiPatterns?
因为这能告诉系统”什么时候不要使用这个 Skill”。
为什么要有 owner 和 lastEvaluatedAt?
因为 Skill 会烂。没有 owner 的 skill 一定会过时,没有评估时间戳的 skill 无法判断是否该下线。
8.4 Skill 的激活和加载
推荐分层激活,而不是一次性全部注入:
- Catalog:系统提示里只挂每个 skill 的
name + description + triggerConditions摘要 - 激活判定:根据对话上下文、用户意图、已绑定能力,缩小到候选集(可以叠加权限/关键词过滤)
- 按需加载:真正需要执行时,模型调
load_skill(name)或运行时自动注入完整 body
这和 §5.1 的 progressive disclosure 是同一套方法。100 个 skill 的完整说明绝不应该同时出现在系统提示里。
8.5 Skill 的版本治理
version 字段不是写着好看的,要配套:
- 灰度发布:新版本先按百分比放量,评估通过才全量
- A/B 对照:同一个任务跑新旧两版,比较成功率/token/延迟
- 回滚:线上出问题能一键切回上一版
- 下线流程:长期未使用或评估回归的 skill 必须有清理机制,不能只增不减
没有灰度和评估的 skill 库,会在半年内退化成”谁都不敢动的考古层”。
8.6 Skill 不应该无限膨胀,但抽象方向要慎重
Skill 系统常见的一个问题是”越做越多,越来越重叠”。
例如有:
research-paperresearch-marketresearch-competitorresearch-security
一种常见的错误直觉是:“应该抽出 search-and-collect / filter-and-rank / extract-key-points / synthesize-report 这种基础能力块,复用到四个 research skill 里。”
但这个方向要慎重——抽象到这个粒度,这些块就已经不是 skill 了,它们更像 tool 或 sub-agent。Skill 的价值在于”领域方法论”(论文 vs 市场 vs 竞品,每个领域有各自的判断标准和输出格式),一旦抽成”过滤并排序”就丢掉了领域信息,反而变成了没灵魂的中间层。
更稳的去重策略:
- 同域合并:把
research-paper按论文类型(理论/实验/综述)合并成带分支的单一 skill - 能力块下沉成 Tool:如果真的是通用动作(搜索、排序、截断摘要),把它做成 Tool 而不是 Skill
- 共享模板文件:输出模板、评分 rubric 这类资源作为 skill 之间共享的文件引用,而不是抽成新 skill
结论:
Skill Library 不应该无限长胖,但”抽通用中间层 skill”经常是错的方向。通用能力应该是 Tool 或 Sub-agent,Skill 要保持”领域方法论”的稀释度。
9. Tool 和 Skill 的评估
9.1 Tool 系统评估指标
重点看这些:
- 工具选择准确率(precision:调了正确的工具)
- 工具选择 specificity(不该调用时没有调用)
- 参数填充正确率
- 工具调用成功率
- 平均调用次数
- Agent trajectory 长度(完成任务用了多少步——衡量 tool 设计是否让模型少走弯路)
- 高风险误操作率
- 调用成本和延迟
- 错误后恢复率(失败一次后是否能靠错误消息自修复)
specificity 经常被忽略——只看”调对工具的准确率”会漏掉一个大类问题:模型调了不该调的工具(比如用户已经给了 DOI 还去搜论文)。这类问题往往是 description 负向约束失效的信号。
9.2 Skill 系统评估指标
- Skill trigger precision
- Skill trigger recall
- 使用 skill 后的任务成功率提升(vs 不用)
- 使用 skill 后的 token / latency 变化
- 过度触发率
- 下游行为一致性(同一个 skill 两次跑的输出偏差)
不要只看”用了多少次”,更要看”用了之后有没有真的更好”。一个高触发但不提升质量的 skill,通常是在浪费 token。
9.3 评估数据集怎么构造
指标有了,数据集怎么来往往更难。以下是实践上的标配:
- Gold set:人工标注”这个 query 应该调用哪个 tool / 应该触发哪个 skill / 参数应该是什么”。规模不用大,50–200 条覆盖主要场景即可,关键是标注质量。
- Adversarial set:故意构造容易误触发的 query。例如用户已经提供 DOI 还问搜论文、用户要”看一下”但其实该修改数据、两个功能相近的 tool 互为诱饵。这部分最能暴露 description 和 negative constraint 的弱点。
- 回归集:每次出现 bug / badcase 都固化成测试样本,新版本必须跑过历史回归。
- LLM-as-judge + 结构化 rubric:用另一个强模型做裁判,但不要让它”打个分”,而是让它按结构化 rubric 逐项判定(工具选对了吗?参数对吗?输出满足模板吗?)。结果应该是 JSON,能被 CI 消费。
- 真实流量采样 + 脱敏回放:生产流量是最好的评估集来源,比造样本真实得多。
几条关键工程规矩:
- 评估必须跑在 CI 上,tool/skill 变更必须跑回归才能合并
- 评估结果要有历史曲线,看趋势而不是单次快照
- 不要用字符串匹配评估输出质量——几乎一定不准
10. Tool 与 Skill 的安全治理
10.1 工具权限分级
所有工具都应该至少标记权限级别,例如:
readwrite_low_riskwrite_high_riskdestructive
分级决定默认行为:read 可以自动执行,write_high_risk 默认必须人工确认,destructive 额外叠加双重确认。
10.2 参数校验
所有 Tool 输入都应做 schema 校验。 如果工具对外部系统有副作用,还要做业务级校验(例如退款金额不能超过订单金额、邮件收件人必须在白名单域)。
10.3 人工确认与可打断
高风险动作建议默认进入人工确认流程,例如:
- 发邮件
- 打款
- 删数据
- 提交生产变更
实现上需要 Agent 运行时支持”interrupt/resume”——tool call 被标记为待审批时,对话进入 pending 状态,用户批准/拒绝后恢复执行。纯阻塞的同步实现会在审批耗时长时搞垮 Agent loop。
10.4 审计日志与 Observability
至少要记录:
- 谁触发的
- 选择了哪个工具/skill
- 参数是什么
- 返回了什么
- 是否自动执行
- 是否经过审批
除了审计日志,生产 Agent 系统还应该有完整的 tracing:
- Tool call 是 span,skill 激活是 span,LLM 调用是 span,父子关系清晰
- OTel / Langfuse / 自研 tracer 任选,关键是每次对话能回放
- 成本和 token 用量归因到 workspace/user 级别
没有 tracing 的 Agent 系统无法定位问题,只能靠猜。
10.5 供应链安全
当你的平台开始支持第三方 skill 或外部 MCP server 时,供应链安全就成了一等问题:
- Skill 从哪里来?有没有被篡改?
- 第三方 MCP server 声明的工具描述里有没有 prompt injection?
- 执行类 tool(shell、code interpreter)的沙箱策略、网络策略、资源配额
这不是理论担忧——已经有公开案例显示恶意 skill/MCP 通过 description 注入操纵 Agent。平台级系统必须在”接入前”做扫描,“接入后”做行为监控。
11. 本章方法论小结
这一章最重要的结论是:
- Tool 是动作、Skill 是方法、Sub-agent 是带独立上下文的专业工种,三者不可互相替代
- Tool 应该单一职责、结构化输入输出、输出受预算约束、错误可操作、写操作幂等、边界清晰
- 工具/Skill 多了以后首选 progressive disclosure,分层路由只作为兜底
- Description 里的负向约束只是软提示,硬约束要靠 tool filtering / schema / server-side 校验
- 异步与长时任务必须走 task_id + 轮询/事件的模式,不要阻塞 Agent loop
- Tool schema 稳定性直接决定 prompt cache 命中率,schema 变更要分版本、后向兼容
- Skill 是可复用的方法论封装,必须配套 catalog + 按需加载 + 版本灰度 + 下线机制
- 抽”通用中间层 skill”经常是错的方向——那些能力应该是 Tool 或 Sub-agent
- 评估不只是指标,更是数据集(gold / adversarial / 回归 / 真实流量)+ CI 回归 + LLM-as-judge with rubric
- 工具与 skill 必须纳入权限、审批、审计、tracing、供应链安全体系,没有 tracing 就等于没法运维